/* image.c - part of ziproxy package
 * 2002 Juraj Variny <variny@anex.sk>
 *
 * Released subject to GNU General Public License
 *
 * Image reading/writing wrappers.
 */

#include <stdio.h>
#include <stdlib.h>

#include "gif_lib.h"
#include "jpeglib.h"
#include "png.h"

enum {err_ok,err_bad_data_format,err_out_of_mem, err_unknown, err_not_impl_yet, while_decomp = 100, while_compress = 200};


int outlen;
char *outbuf;

typedef struct {
		char* buf;
		int size;
		union {
			int pos;
			char jpeg_unexpected_end;
		}x;
}InputDesc ;

void png_read_data(png_structp png_ptr,
        png_bytep data, png_size_t length)
{
		InputDesc *desc=(InputDesc*)png_get_io_ptr(png_ptr);

		if(desc->x.pos + length >= desc->size) png_error(png_ptr, "Reading past input buffer\n");
		
	memcpy(data, desc->buf + desc->x.pos,length);
	desc->x.pos += length;
}



int png2bitmap(char *inbuf, int insize, char** image, int *width, int *height, 
				int *bytes_per_pixel)
{
  	png_structp png_ptr;
   	png_infop info_ptr, end_info;
   	int color_type, grayscale, num_palette, bit_depth;
	png_color *palette;
	png_bytep **row_pointers, onerow;
	int i;
	InputDesc desc;

   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
      NULL, NULL, NULL);
   if(!png_ptr)
   {
		   fprintf(stderr,"Out of memory\n");
			return err_out_of_mem + while_decomp;
   }
    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
    {
		   fprintf(stderr,"Out of memory\n");
			return err_out_of_mem + while_decomp;
   }
	if (setjmp(png_jmpbuf(png_ptr)))
    {
        png_destroy_read_struct(&png_ptr, &info_ptr,
           &end_info);
		fprintf(stderr,"Error reading PNG file\n");
		return err_bad_data_format + while_decomp;
    }
	desc.buf=inbuf;
	desc.size=insize;
	desc.x.pos=0;

	png_set_read_fn(png_ptr,(voidp)&desc,png_read_data);
	
	png_read_info(png_ptr,info_ptr);
	png_get_IHDR(png_ptr,info_ptr,width,height,&bit_depth,&color_type,
					NULL,NULL,NULL);

	grayscale = 1;
	
	if (color_type & PNG_COLOR_MASK_PALETTE)
	{
			//TODO better palette->grayscale expansion
			//Detects grayscale image (scans all colors in palette)
/*			png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
			for(i=0;i<num_palette;i++)
					if(palette[i].red != palette[i].green ||
								palette[i].green != palette[i].blue)
					{
							grayscale=0;
							break;
					}*/
			png_set_palette_to_rgb(png_ptr);
	}
	
	if (color_type == PNG_COLOR_TYPE_GRAY || 
					color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
	{
		grayscale=1;	
        if(bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png_ptr);
	}else grayscale=0;
	
	if (bit_depth == 16)
        png_set_strip_16(png_ptr);
	
	 if (color_type & PNG_COLOR_MASK_ALPHA)
        png_set_strip_alpha(png_ptr);
	 

	if (grayscale) *bytes_per_pixel = 1;
		else *bytes_per_pixel = 3;

	 row_pointers = malloc(*height * sizeof(png_bytep));
	
	*image=malloc((*width) * (*height) * (*bytes_per_pixel));
	onerow=*image;

   for (i=0; i < *height; i++)
   {
      	row_pointers[i]=onerow;
   		onerow += (*width) * (*bytes_per_pixel);
	}
   png_read_image(png_ptr,row_pointers);

   //something is wrong with this   
//   free(row_pointers);

   png_destroy_read_struct(&png_ptr, &info_ptr,NULL);

	return err_ok;
}


int gif_mem_input(GifFileType *GifFile, GifByteType* data, int bytes)
{
	InputDesc *desc = (InputDesc*) GifFile->UserData;
	int copied;
	
	if(bytes + desc->x.pos >= desc->size) copied = desc->size - desc->x.pos;
	else copied=bytes;
	
	
	memcpy(data,desc->buf + desc->x.pos,copied);

	desc->x.pos+=copied;

	return copied;
}

int gif2bitmap(char *inbuf, int insize, char** image, int *width, int *height, 
				int *bytes_per_pixel)
{
		GifFileType *GifFile;
		SavedImage* LastImg = NULL;
		GifImageDesc *LastImgDesc = NULL;
		ColorMapObject *Colors;
		GifColorType * BackGround, *Pixel;
		InputDesc desc;
		int i,j,c,offset;
		char* BufferP;
		const int    InterlacedOffset[] = { 0, 4, 2, 1 }; /* The way Interlaced image should. */
		const int    InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */

		desc.buf=inbuf;
		desc.size=insize;
		desc.x.pos=0;

		if ((GifFile = DGifOpen((void*)&desc, &gif_mem_input)) == NULL) 
	    return( err_unknown+ while_decomp);//more possible reasons
 
		if(DGifSlurp(GifFile)!=GIF_OK)
		{
				fprintf(stderr,"GIF reading failed\n");
				return(err_bad_data_format + while_decomp);
		}
		*width=GifFile->SWidth;
		*height=GifFile->SHeight;

		//We're interested in last frame. Searching for last complete redraw
		//of image
		for(i = GifFile->ImageCount - 1; i>=0; i--)
				if(GifFile->SavedImages[i].ImageDesc.Left == 0 &&
					GifFile->SavedImages[i].ImageDesc.Top == 0 &&
					GifFile->SavedImages[i].ImageDesc.Width == *width &&
					GifFile->SavedImages[i].ImageDesc.Height == *height
					)
						LastImg = GifFile->SavedImages + i;
		if(!LastImg) //no complete image found - we won't mess with it
		{
				DGifCloseFile(GifFile);
				return(err_not_impl_yet + while_decomp);
		}
		LastImgDesc = &LastImg->ImageDesc;
		
		Colors = (LastImgDesc->ColorMap
			? LastImgDesc->ColorMap
			: GifFile->SColorMap);

		BackGround = Colors->Colors + GifFile->SBackGroundColor;

		*bytes_per_pixel=1;
		for(i = 0; i < Colors->ColorCount; i++)
		{
				if(( Colors->Colors[i].Red !=  Colors->Colors[i].Green ) ||
					( Colors->Colors[i].Green != Colors->Colors[i].Blue))
				{
					*bytes_per_pixel=3;
					break;
				}
		}
		
		*image=malloc((*width)*(*height)*(*bytes_per_pixel));
		BufferP=*image;
		offset = -1;
		
		if(GifFile->Image.Interlace){
				
			    for (c = 0; c < 4; c++)
			for (i = InterlacedOffset[c]; i < *height;
						 i += InterlacedJumps[c])
			{
					BufferP = *image + i*(*width)*(*bytes_per_pixel);
				for(j=0; j < *width; j++)
				{
						offset++;
						Pixel = Colors->Colors + ((unsigned char *)LastImg->RasterBits)[offset];
						*BufferP++ = Pixel->Red;
						if(*bytes_per_pixel == 1) continue;
						
						*BufferP++ = Pixel->Green;
						*BufferP++ = Pixel->Blue;
				}
			}

		}else{
		for(i=0; i < *height; i++)
			for(j=0; j < *width; j++)
			{
					offset++;
						Pixel = Colors->Colors + ((unsigned char *)LastImg->RasterBits)[offset];
						*BufferP++ = Pixel->Red;
						if(*bytes_per_pixel == 1) continue;
						
						*BufferP++ = Pixel->Green;
						*BufferP++ = Pixel->Blue;
									}
		}
		
		DGifCloseFile(GifFile);
		return err_ok;
}

InputDesc * get_src (InputDesc* ptr)
{
		static InputDesc *myptr;

		if (ptr) myptr = ptr;
		return myptr;
}

void jpeg_src_noop(j_decompress_ptr dinfo){}

void jpeg_src_init(j_decompress_ptr dinfo)
{
		InputDesc * desc = get_src(NULL);

		dinfo-> src ->next_input_byte = desc->buf;
		dinfo-> src -> bytes_in_buffer = desc->size;
}

boolean jpeg_src_fill_input(j_decompress_ptr dinfo)
{
		return FALSE;
}

void jpeg_src_skip_input (j_decompress_ptr dinfo, long num_bytes)
{
		InputDesc * desc = get_src(NULL);

		if ((char*)(dinfo->src->next_input_byte + num_bytes) > desc->buf + desc->size || 
				(char*)dinfo->src->next_input_byte == &desc->x.jpeg_unexpected_end )
		{ //silly behaviour - recommended by jpeglib docs
			dinfo->src->next_input_byte = &desc->x.jpeg_unexpected_end;
			dinfo->src->bytes_in_buffer = 1; 
		}
		else
		{
			dinfo->src->next_input_byte += num_bytes;
			dinfo->src->bytes_in_buffer -= num_bytes;
		}
}

jmp_buf errjmp;

void jpeg_error_exit (j_common_ptr cinfo)
{
		longjmp(errjmp,1);
}

void jpeg_emit_message (j_common_ptr cinfo, int msg_level)
{}



int jpg2bitmap(char *inbuf, int insize, char** image, int *width, int *height, 
				int *bytes_per_pixel)
{
		
        struct jpeg_decompress_struct dinfo;
		char *BufferP;
        struct jpeg_error_mgr jerr;
		struct jpeg_source_mgr jsrc;
		InputDesc desc;
		int imgsize,row_stride;

		desc.buf = inbuf;
		desc.size = insize;
		desc.x.jpeg_unexpected_end = JPEG_EOI;

		get_src(&desc);

		dinfo.err = jpeg_std_error(&jerr);
		dinfo.err->error_exit = jpeg_error_exit;
		dinfo.err->emit_message = jpeg_emit_message;
		
        jpeg_create_decompress(&dinfo);

		jsrc.init_source = jpeg_src_init;
		jsrc.term_source = jpeg_src_noop; 
		jsrc.fill_input_buffer = jpeg_src_fill_input;
		jsrc.skip_input_data = jpeg_src_skip_input;
		dinfo.src = &jsrc;
		
		if (setjmp(errjmp))
				return err_unknown + while_decomp;

		jpeg_read_header(&dinfo, TRUE);
		jpeg_start_decompress(&dinfo);
		
		imgsize=dinfo.output_width*dinfo.output_height*dinfo.output_components;
		row_stride=dinfo.output_width*dinfo.output_components;
		
		*width=dinfo.output_width;
		*height=dinfo.output_height;
		*bytes_per_pixel=dinfo.output_components;
		
		*image = malloc (imgsize);
		BufferP=*image;
		while (dinfo.output_scanline < dinfo.output_height)
		{
				jpeg_read_scanlines(&dinfo,&BufferP,1);
				BufferP+=row_stride;
		}
		
		jpeg_finish_decompress(&dinfo);
		jpeg_destroy_decompress(&dinfo);
		return err_ok;
}

void jpeg_dest_init(j_compress_ptr cinfo)
{
	outbuf = cinfo->dest->next_output_byte = malloc(outlen);
	cinfo->dest->free_in_buffer=outlen;
}

boolean jpeg_dest_empty_output_buffer()
{
		return FALSE; //output file larger than original..emits a warning message
}

void jpeg_dest_term(j_compress_ptr cinfo)
{
		outlen = outlen - cinfo->dest->free_in_buffer;
}

int bitmap2jpg(char* image, int width, int height, 
				int bytes_per_pixel, int quality)
{
		struct jpeg_compress_struct cinfo;
        struct jpeg_error_mgr jerr;
		struct jpeg_destination_mgr memdest;
		int row_stride = width*bytes_per_pixel;
		int i;

		cinfo.err = jpeg_std_error(&jerr);
		cinfo.err->error_exit = jpeg_error_exit;
		cinfo.err->emit_message = jpeg_emit_message;

		if(setjmp(errjmp))
				return err_unknown + while_compress;

		jpeg_create_compress(&cinfo);
		
		memdest.init_destination = jpeg_dest_init;
		memdest.empty_output_buffer = jpeg_dest_empty_output_buffer;
		memdest.term_destination = jpeg_dest_term;
		
		cinfo.dest = &memdest;
		
		cinfo.image_width = width; 	/* image width and height, in pixels */
		cinfo.image_height = height;
		if ((cinfo.input_components =bytes_per_pixel)== 3)	
		cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
		else cinfo.in_color_space = JCS_GRAYSCALE;
		
		
	jpeg_set_defaults(&cinfo);
	
	if(quality > 100){
			quality -=128;
			cinfo.smoothing_factor = 10;
	}
	
	cinfo.optimize_coding = TRUE;
	jpeg_set_quality(&cinfo, quality, FALSE );

	jpeg_start_compress(&cinfo, TRUE);

	for(i=0;(i == cinfo.next_scanline) && (cinfo.next_scanline < cinfo.image_height);i++)
	{
			jpeg_write_scanlines(&cinfo,&image,1);
			image+=row_stride;
	}
		
	if (i < cinfo.image_height)
	{
			jpeg_abort_compress(&cinfo);
			outlen = -1;
			free(outbuf);
			return err_out_of_mem + while_compress;
	}

	jpeg_finish_compress(&cinfo);
	jpeg_destroy_compress(&cinfo);
	return err_ok;
}
